/* eslint-disable @typescript-eslint/no-loop-func */
/**
 * 预览文件信息并发送
 */
import {
  FileTextTwoTone,
  DeleteOutlined,
  LoadingOutlined,
  WarningOutlined,
  CheckCircleOutlined,
} from '@ant-design/icons'
import { getLogger } from '@gnlab/utils'
import { useRefValue } from '@gnlab/utils/env-react'
import { Button, Tooltip } from 'antd'
import { OrderedMap } from 'immutable'
import config from '@/config'
import { ImportantEditModal } from '@/desktop/components'
import Link from '@/desktop/components/link'
import { formatFileSize } from '@/lib/utils'
import services from '@/services'
import { ChatRecordType } from '@/services/contact-chat'
import './index.module.less'

const logger = getLogger('ui/file-send-preview')

/**
 * ======================
 * 待发送项目信息
 * ======================
 */

enum ItemStatus {
  Preview = 1, // 待发送
  Uploading = 2, // 上传中
  Exceed = 3, // 文件过大，不允许发送
  UploadFailed = 4, // 上传失败
  Sended = 5, // 已发送
}

interface ItemInfo {
  key: string
  name: string
  size: number
  localUrl: string | null // 不允许发送的文件不生成 object url 以节省资源
  type: ReturnType<typeof parseFileType>
  status: ItemStatus
}

function makeItemMap(files: File[]): OrderedMap<File, ItemInfo> {
  return OrderedMap<File, ItemInfo>(
    files.map((file, index) => [file, makeItemInfo(index.toString(), file)]),
  )
}

function makeItemInfo(key: string, file: File): ItemInfo {
  const isExceed = file.size > 1024 * 1024 * config.MAX_FILESIZE
  const info: ItemInfo = {
    key,
    name: file.name,
    size: file.size,
    localUrl: isExceed ? null : URL.createObjectURL(file),
    type: parseFileType(file),
    status: isExceed ? ItemStatus.Exceed : ItemStatus.Preview,
  }
  logger.debug('file', info, file)
  return info
}

function parseFileType(file: File) {
  const mime = file.type
  if (mime.endsWith('jpeg') || mime.endsWith('png')) {
    return ChatRecordType.Image
  } else if (mime.endsWith('gif')) {
    return ChatRecordType.Emotion
  } else if (mime.startsWith('video')) {
    return ChatRecordType.Video
  } else if (
    mime.startsWith('audio/mpeg') ||
    mime.startsWith('audio/aac') ||
    mime.startsWith('audio/x-mpeg') ||
    mime.startsWith('audio/x-aac')
  ) {
    return ChatRecordType.Audio
  } else {
    return ChatRecordType.File
  }
}

/*
======================
发送/预览状态
======================

关闭（没有待发送文件）
-> 不可开始（提供的文件都不合法）
-> 未开始（有至少一个合法文件；若开启了 autoStart，会自动开始发送；可删除文件使状态变化）
  -> 关闭（因所有文件都被删除）
  -> 发送中（有“上传中“的文件）
    -> 关闭（所有文件状态都为“发送成功”，也没有不合法文件）
    -> 部分成功（有发送失败的或不合法的文件，则保留界面不自动关闭）
*/

enum ProcessStatus {
  Close = 'Close',
  CannotStart = 'CannotStart',
  Idle = 'Idle',
  Sending = 'Sending',
  AllSuccess = 'AllSuccess',
  PartialSuccess = 'PartialSuccess',
}

function calcuteProcessStatus(items: OrderedMap<File, ItemInfo>) {
  if (!items.size) return ProcessStatus.Close // 后续不会没有文件
  if (items.every(v => v.status === ItemStatus.Exceed)) return ProcessStatus.CannotStart // 后续不会只有 Exceed
  if (items.find(v => v.status === ItemStatus.Preview)) return ProcessStatus.Idle // 后续不会有 Preview
  if (items.find(v => v.status === ItemStatus.Uploading)) return ProcessStatus.Sending // 后续也不会有 Uploading
  if (items.every(v => v.status === ItemStatus.Sended)) return ProcessStatus.Close // 后续不会只有 Sended
  return ProcessStatus.PartialSuccess
}

/**
 * ======================
 * 其他工具函数
 * ======================
 */

/**
 * 发送聊天消息
 */
function sendItem(type: ChatRecordType, url: string) {
  if (type === ChatRecordType.Image) void services.chat.send(ChatRecordType.Image, { url })
  else if (type === ChatRecordType.Emotion)
    void services.chat.send(ChatRecordType.Emotion, { name: '动画表情', url })
  else if (type === ChatRecordType.Video) void services.chat.send(ChatRecordType.Video, { url })
  else if (type === ChatRecordType.Audio) void services.chat.send(ChatRecordType.Audio, { url })
  else {
    const nodes = url.split('/')
    const name = (nodes[nodes.length - 1] ?? '') || url
    void services.chat.send(ChatRecordType.File, { name, url, size: 0 })
  }
}

// =====================================================

export interface PreviewingInfo {
  files: File[] // 要发送的文件
  recordType?: ChatRecordType // 明确指定要发送的消息类型
  autoStart?: boolean // 是否自动开始发送
  onFinish: () => void // 执行或取消发送后会触发此回调
}

export default function FileSendPreview(props: { previewing: PreviewingInfo | null }) {
  const { previewing } = props

  // 每当 previewing 有变化，都更新 previewing key，以保证完全重新生成 <Content />
  const [previewingKey, setPreviewingKey] = useState(0)
  useEffect(() => setPreviewingKey(key => key + 1), [previewing])

  return <ImportantEditModal open={!!previewing} onCancel={previewing?.onFinish} title="发送预览">
    {previewing ? <Content key={previewingKey} previewing={previewing} /> : null}
  </ImportantEditModal>
}

function Content(props: { previewing: PreviewingInfo }) {
  const { previewing } = props

  const [items, setItems] = useState(() => makeItemMap(previewing.files)) // 初始必须有内容，不然计算状态会直接进入 Close 状态
  const itemsRef = useRefValue(items)
  const removeItem = useCallback((file: File) => setItems(items.delete(file)), [items])

  const status = useMemo(() => calcuteProcessStatus(items), [items])

  const cancelRef = useRef(false) // 标记退出状态，退出后不再执行后续发送行为
  const startSend = useCallback(() => {
    const items = itemsRef.current
    let updatedItems = items
    for (const [file, item] of items) {
      if (item.status !== ItemStatus.Preview) continue
      const setStatus = (status: ItemStatus) => {
        updatedItems = updatedItems.set(file, { ...item, status })
        setItems(updatedItems)
      }
      setStatus(ItemStatus.Uploading)
      void services.utils.upload(file).then(res => {
        if (cancelRef.current) return
        if (res.success) {
          sendItem(previewing.recordType ?? item.type, res.data)
          setStatus(ItemStatus.Sended)
        } else {
          setStatus(ItemStatus.UploadFailed)
        }
      })
    }
  }, [previewing])

  // 响应状态变化
  useEffect(() => {
    if (status === ProcessStatus.Idle && (previewing.autoStart ?? false)) {
      // 通过 setTimeout 解决 StrictMode 下重复触发 useEffect() 导致重复发送的问题
      // 如果触发 useEffect() 后立刻重新触发，上一次发送行为会被取消。
      const timeoutId = setTimeout(() => startSend())
      return () => clearTimeout(timeoutId)
    }
    if (status === ProcessStatus.Close) previewing.onFinish()
  }, [status, previewing, startSend])

  // 执行收尾工作
  useEffect(() => {
    cancelRef.current = false
    return () => {
      cancelRef.current = true
      // eslint-disable-next-line ts-react-hooks/exhaustive-deps
      itemsRef.current
        .filter(item => item.localUrl !== null)
        .map(item => URL.revokeObjectURL(item.localUrl!))
    }
  }, [])

  // =========================================

  let actions: JSX.Element | null = null
  if (status === ProcessStatus.CannotStart || status === ProcessStatus.PartialSuccess) {
    actions = <Button onClick={previewing.onFinish}>关闭</Button>
  } else if (status === ProcessStatus.Idle || status === ProcessStatus.Sending) {
    actions = <>
      <Button type="primary" onClick={startSend} loading={status === ProcessStatus.Sending}>
        发送
      </Button>
      <Button onClick={previewing.onFinish}>取消</Button>
    </>
  }

  return <>
    <main>
      {[...items].map(([file, info]) => <Info
        key={info.key}
        info={info}
        onRemove={() => removeItem(file)}
      />)}
    </main>
    <footer>{actions}</footer>
  </>
}

function Info(props: { info: ItemInfo; onRemove: () => void }) {
  const { info, onRemove } = props

  return <div styleName="info">
    <div styleName="preview">
      {info.type === ChatRecordType.Image ? (
        <Link href={info.localUrl ?? ''}>
          <img src={info.localUrl ?? ''} />
        </Link>
      ) : (
        <FileTextTwoTone />
      )}
    </div>
    <div styleName="name">{info.name}</div>
    <div styleName="size">{formatFileSize(info.size)}</div>
    <div styleName="status">
      {info.status === ItemStatus.Preview ? (
        <DeleteOutlined onClick={onRemove} styleName="remove" />
      ) : null}
      {info.status === ItemStatus.Uploading ? <LoadingOutlined styleName="uploading" /> : null}
      {info.status === ItemStatus.Exceed ? (
        <Tooltip title={`文件大于${config.MAX_FILESIZE}M，无法发送`}>
          <WarningOutlined styleName="exceed" />
        </Tooltip>
      ) : null}
      {info.status === ItemStatus.UploadFailed ? (
        <Tooltip title="文件上传失败">
          <WarningOutlined styleName="upload-failed" />
        </Tooltip>
      ) : null}
      {info.status === ItemStatus.Sended ? <CheckCircleOutlined styleName="sended" /> : null}
    </div>
  </div>
}
